package cas.ihep.util;

import java.io.Closeable;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Objects;

public class Finalizer implements Closeable {


    private LinkedList<Closeable> stack;
    private Throwable thrown;

    public static Finalizer create(){
        return new Finalizer();
    }

    private Finalizer(){
        stack=new LinkedList<>();
    }

    public void rethrow(Throwable e)throws IOException{
        thrown=e;
        if(Objects.nonNull(e)) {
            throwIfPossible(e, IOException.class);
            throw new RuntimeException(e);
        }
    }

    public <E extends Exception> void rethrow(Throwable e,Class<E> klass)throws IOException,E{
        thrown=e;
        if(Objects.nonNull(e)) {
            throwCast(e, IOException.class);
            throwIfPossible(e, klass);
            throw new RuntimeException(e);
        }
    }

    public <E1 extends Exception,E2 extends Exception> void rethrow(Throwable e,Class<E1> e1lass,Class<E2> e2lass)throws IOException,E1,E2{
        thrown=e;
        if(Objects.nonNull(e)) {
            throwCast(e, IOException.class);
            throwCast(e, e1lass);
            throwIfPossible(e, e2lass);
            throw new RuntimeException(e);
        }
    }

    @Override
    public void close() throws IOException {
        Throwable throwable=thrown;
        for(Closeable c:stack){
            try{
                c.close();
            }catch (Throwable t){
                if(throwable==null){
                    throwable=t;
                }else{
                    throwable.addSuppressed(t);
                }
            }
        }
        stack.clear();
        if(thrown==null && throwable!=null){
            throwCast(throwable,IOException.class);
        }
    }

    public <C extends Closeable> void cancel(C c){
        if(Objects.nonNull(c)) {
            stack.remove(c);
        }
    }

    public <C extends Closeable> C register(C c){
        if(Objects.nonNull(c)) {
            stack.addFirst(c);
        }
        return c;
    }

    public <C extends Closeable> C registerLast(C c){
        if(Objects.nonNull(c)) {
            stack.addLast(c);
        }
        return c;
    }

    public <C extends Closeable> void registerAll(Collection<C> list,boolean reverse){
        if(reverse) {
            for (C aList : list) {
                if(aList!=null) {
                    stack.addFirst(aList);
                }
            }
        }else{
            int i=0;
            for(C c:list){
                if(c!=null) {
                    stack.add(i++, c);
                }
            }
        }
    }

    private static <E extends Throwable> void throwCast(Throwable t,Class<E> klass)throws E{
        if(klass.isInstance(t)){
            throw klass.cast(t);
        }
    }

    private static <E extends Throwable> void throwIfPossible(Throwable e,Class<E> klass)throws E{
        throwCast(e,klass);
        throwCast(e,Error.class);
        throwCast(e,RuntimeException.class);
    }

}
